DMMF: ステートマシン
このとき、ある状態から別の状態へのパス(遷移)は、何らかの コマンド によって引き起こされる https://scrapbox.io/files/669f3a69b8117e001d16d779.png
言語パーサや正規表現で使われるような、何十・何百もの状態を持つ複数なものではない
せいぜい数個のケースと、ケースより少し多い程度の状態遷移があるだけ
e.g. メールアドレスの「未検証」と「検証」
https://scrapbox.io/files/669f3c6b1854a4001d78ec85.png
e.g. ショッピングカートの「空」と「アクティブ」、「支払い済み」
https://scrapbox.io/files/669f3cd6bd6f3e001d78c68c.png
e.g. 荷物の配達の「未配達」と「配達中」、「配達完了」の 3 つの状態
https://scrapbox.io/files/669f3d341854a4001d78f64f.png
なぜステートマシンを使うのか?
1. それぞれの状態において、受け取ける処理を変えられる
「アクティブ」状態のときだけ支払い可能
状態ごとに別の型を使用することで、その要件を関数のシグネチャに直接エンコードできる
これにより、コンパイラを使用してそのビジネスルールが遵守されることを確認できる
2. すべての状態が明示的に文書化される
暗黙の了解で文書化されていない状態があることはよくある
「空」のカートと「アクティブ」のカートの区別
あまり文書化されない
3. 起こり得る状況をすべて考慮にいれて設計するよう強く促される
状態の観点から設計を考えることで、エッジケースの考慮漏れに気がつける
既に認証されているメールを認証するとどうなる?
空のショッピングカートから商品を取り出そうとするとどうなる?
すでに「発送済み」の状態になっている商品を発送しようとするとどうなる?
F# でシンプルなステートマシンを実装する方法
ビジネス向けのシンプルなステートマシンは、特別なツールなライブラリは必要ない
具体的にどう実装する?
各状態を独自の型で表現し、関連するデータがあればそれを格納する
code:fsharp
type Item = ...
type ActiveCartData = { UnpaidItems: Item list }
type PaidCartData = { PaidItems: Item list; Payment: float }
一連の状態は、各状態をケースに持つ選択型で表される code:fsharp
type ShoppingCart =
| EmptyCart
| ActiveCart of ActiveCartData
| PaidCart of PaidCartData
ステートマシン全体(選択型)を受け取り、新しいバージョン(更新された選択型)を返す関数として実装
受け取った値をそのまま返すこともある
e.g. カートに商品を追加
code:fsharp
let addItem cart item =
match cart with
| EmptyCart ->
ActiveCart { UnpaidItems = item } | ActiveCart { UnpaidItems = existingItems } ->
ActiveCart { UnpaidItems = item :: existingItems }
| PaidCart _ ->
cart
e.g. カートに入っている商品を支払う
code:fsharp
let makePayment cart payment =
match cart with
| EmptyCart ->
cart
| ActiveCart { UnpaidItems = existingItems } ->
PaidCart
{ PaidItems = existingItems
Payment = payment }
| PaidCart _ ->
cart